package core.redeNeural;

import org.encog.matrix.Matrix;
import org.encog.neural.activation.ActivationSigmoid;
import org.encog.neural.data.NeuralData;
import org.encog.neural.data.basic.BasicNeuralData;
import org.encog.neural.networks.BasicNetwork;
import org.encog.neural.networks.Layer;
import org.encog.neural.networks.layers.BasicLayer;
import org.encog.neural.networks.layers.FeedforwardLayer;

import core.exception.RedeException;

import sun.security.x509.AVA;

import java.io.Serializable;
import java.util.Random;
import java.util.StringTokenizer;

public class RedeNeural implements Comparable<RedeNeural>, Serializable{

	private static final long serialVersionUID = -5056196630789671786L;
	protected static final int NUM_LAYERS = 4;
	private static final int LAYER1 = 32;
	private static final int LAYER2 = 20;
	private static final int LAYER3 = 10;
	private static final int LAYER4 = 1;
	private String codigo;
	private double fitness;
	private BasicNetwork net = new BasicNetwork();
	private double pesoDama;

	public RedeNeural(String codigo){
		if(codigo.length() != 7048)
			try {
				throw new RedeException("Esse é um código de rede tipo 2!");
			} catch (RedeException e) {
				e.printStackTrace();
			}

			this.codigo = codigo;

			int sinalPeso;
			int pesoInt=0;
			FeedforwardLayer entrada = new FeedforwardLayer(LAYER1);
			FeedforwardLayer int1 = new FeedforwardLayer(LAYER2);
			FeedforwardLayer int2 = new FeedforwardLayer(LAYER3);
			FeedforwardLayer saida = new FeedforwardLayer(LAYER4);
			net.addLayer(entrada);
			net.addLayer(int1);
			net.addLayer(int2);
			net.addLayer(saida);
			//Recupera os pesos da rede
			int i;
			for(i = 0; i < codigo.length() - 8;i=i+8){ 
				pesoInt = 0 ;

				String pesoBin = codigo.substring(i, i+8);
				String pesoBinSemSinal = pesoBin.substring(1, 8);

				//Verificacao de sinal dos pesos
				if(pesoBin.charAt(0)=='1')
					sinalPeso=-1;
				else
					sinalPeso=1;

				pesoInt = Integer.parseInt(pesoBinSemSinal,2);

				//Atribuicao de sinal aos pesos
				pesoInt = pesoInt*sinalPeso;
				double pesoReal = (double)pesoInt;

				pesoReal = pesoReal/127;

				int iAux = i/8;
				//Insercao dos pesos j� convertidos para numeros reais com sinal
				//para a posicao correta na matriz correspondente
				if(iAux<(LAYER1+1)*LAYER2)
					net.getLayers().get(0).getMatrix().set(iAux/(LAYER2),iAux%(LAYER2), pesoReal);
				else 
					if(iAux<(LAYER1+1)*LAYER2+(LAYER2+1)*LAYER3)
						net.getLayers().get(1).getMatrix().set((iAux-(LAYER1+1)*LAYER2)/(LAYER3),(iAux-(LAYER1+1)*LAYER2)%(LAYER3), pesoReal);						
					else
						net.getLayers().get(2).getMatrix().set((iAux-((LAYER1+1)*LAYER2+(LAYER2+1)*LAYER3))/(LAYER4),(iAux-((LAYER1+1)*LAYER2+(LAYER2+1)*LAYER3))%LAYER4, pesoReal);				
			}	
//			for(Layer f : net.getLayers()){
//				if(!f.isOutput())
//					for(int a = 0; a < f.getMatrix().getRows(); a++){
//						for(int b = 0; b < f.getMatrix().getCols(); b ++){
//							System.out.print(f.getMatrix().get(a, b) + " ");
//						}					
//						System.out.println("");
//					}
//			}

			//Recupera o peso da dama -- REDE TIPO 2
			//		double pesoDama = Integer.parseInt(codigo.substring(i, i+8),2);
			//		setPesoDama(normalizar(pesoDama/255));
	}

	public RedeNeural(){		
		FeedforwardLayer entrada = new FeedforwardLayer(LAYER1);
		FeedforwardLayer int1 = new FeedforwardLayer(LAYER2);
		FeedforwardLayer int2 = new FeedforwardLayer(LAYER3);
		FeedforwardLayer saida = new FeedforwardLayer(LAYER4);
		net.addLayer(entrada);
		net.addLayer(int1);
		net.addLayer(int2);
		net.addLayer(saida);		
		net.reset();
		StringBuilder redeCodificada = new StringBuilder();
		for(int l=0; l < NUM_LAYERS -1; l++){
			for(int i=0; i<net.getLayers().get(l).getMatrix().getRows();i++){
				for(int j=0; j<net.getLayers().get(l).getMatrix().getCols();j++){
					double pesoReal = net.getLayers().get(l).getMatrix().get(i, j);
					pesoReal=pesoReal*127;
					int pesoInt = (int)pesoReal;
					String msBit;
					if(pesoInt>=0){
						msBit = "0";						
					}else{
						msBit = "1";
						pesoInt=-pesoInt;
					}
					String pesoBin = Integer.toBinaryString(pesoInt);
					StringBuilder gene = new StringBuilder();
					gene.append(msBit);
					for(int k=0;k<7-pesoBin.length();k++){
						gene.append("0");
					}				
					gene.append(pesoBin);				
					redeCodificada.append(gene);
				}
			}
		}
		//REDE TIPO 2
		//		Random r = new Random();
		//		double aux = r.nextDouble();
		//		setPesoDama(normalizar(aux));
		//		int valorInteiro = (int) (aux*255);
		//		String pesoBin = Integer.toBinaryString(valorInteiro);
		//		StringBuilder genePesoDama = new StringBuilder();
		//		for(int k=0;k<8-pesoBin.length();k++){
		//			genePesoDama.append("0");
		//		}
		//		genePesoDama.append(pesoBin);
		//		redeCodificada.append(genePesoDama);		
		//		this.codigo=redeCodificada.toString();
	}

	/**
	 * Normaliza o peso escolhido para o intervalo
	 * [1,1;10]
	 * @param aux
	 * @return
	 */
	private double normalizar(double aux) {
		return (aux*8.9) + 1.1;
	}

	public String getCodigo() {
		return codigo;
	}

	public void setCodigo(String codigo) {
		this.codigo = codigo;
	}

	public double getFitness() {
		return fitness;
	}

	public void setFitness(double fitness) {
		this.fitness = fitness;
	}

	public void incrementaFitness(int incremento){
		this.fitness += incremento;
	}

	//@Override
	public int compareTo(RedeNeural outraRede) {
		if(this.fitness > outraRede.fitness)
			return 1;
		if(this.fitness < outraRede.fitness)
			return -1;
		return 0;
	}

	public double avaliaTabuleiro(double casas[]){
		double[] casasPesoDama = new double[32];
		double somaSaida = 0;		

		//REDE TIPO 1
		NeuralData data = new BasicNeuralData(32);
		data.setData(casas);
		data = net.compute(data);
		return data.getData(0);

		//REDE TIPO 2
		//		for(int i = 0; i<32; i++){
		//			if(Math.abs(casas[i]) == 2)
		//				casasPesoDama[i] = (casas[i]/2)*getPesoDama();
		//			else
		//				casasPesoDama[i] = casas[i];
		//			somaSaida += casasPesoDama[i];
		//		}
		//		NeuralData data = new BasicNeuralData(32);
		//		data.setData(casasPesoDama);
		//		net.compute(data);
		//		
		//		for(double a : net.getLayers().get(NUM_LAYERS - 2).getFire().getData())
		//			somaSaida += a;		
		//		ActivationSigmoid funcaoAtivacao = new ActivationSigmoid();		
		//		return funcaoAtivacao.activationFunction(somaSaida);

	}


	public BasicNetwork getNet() {
		return net;
	}

	public void setNet(BasicNetwork net) {
		this.net = net;
	}

	public static void main(String args[]){
		RedeNeural teste1 = new RedeNeural();

		RedeNeural teste2 = new RedeNeural(teste1.codigo);

		final double[] CASAS_INICIAIS={
				0,0,-1,-2,1,0,0,2,1,1,-1,-2,1,0,0,2,1,1,-1,-2,1,0,0,2,1,1,-1,-2,1,0,0,2};

		NeuralData data = new BasicNeuralData(32);
		data.setData(CASAS_INICIAIS);
		NeuralData resposta = teste1.getNet().compute(data);
		System.out.println(teste1.avaliaTabuleiro(CASAS_INICIAIS));
		System.out.println(teste1.getPesoDama());
		System.out.println(teste2.avaliaTabuleiro(CASAS_INICIAIS));
		System.out.println(teste2.getPesoDama());
	}

	public double avaliaTabuleiro(int[] casas) {
		double[] doubleCasas = new double[32];
		for(int i=0;i<32;i++)
			doubleCasas[i] = casas[i];		
		return avaliaTabuleiro(doubleCasas);
	}

	public double getPesoDama() {
		return pesoDama;
	}

	public void setPesoDama(double pesoDama) {
		this.pesoDama = pesoDama;
	}

}
